home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / sgiCD / cdreader.c < prev    next >
C/C++ Source or Header  |  1994-08-01  |  15KB  |  524 lines

  1. /*
  2.  *    cdreader - Plays an audio CD through the SGI Indigo speaker.
  3.  *    I wrote this to learn about the SGI CD audio library routines.
  4.  *
  5.  *    Original Author:  Patrick Wolfe (pwolfe@kai.com, uunet!kailand!pwolfe)
  6.  *    You are free to use this source code/program as you wish, at your own
  7.  *    risk.  Don't blame me for any mistakes in the code (or anything else, 
  8.  *      for that matter).
  9.  */
  10.  
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <sys/types.h>
  14. #include <cdaudio.h>
  15. #include <signal.h>
  16. #include <audio.h>
  17. #include <getopt.h>
  18.  
  19. /* number of samples in each frame is datasize divided by 2, since there are two bytes per sample */
  20. #define SAMPS_PER_FRAME (CDDA_DATASIZE / 2)
  21.  
  22. /* warn if the current volume is below this, or the user may think this program doesn't work! */
  23. #define LOW_VOLUME    7
  24.  
  25. /* constants for identifying fields in the start_at and stop_at arrays */
  26. #define POS_TRACK    0
  27. #define POS_MINUTE    1
  28. #define POS_SECOND    2
  29. #define POS_FRAME    3
  30.  
  31. int read_size;            /* the number of frames to read at one time (CDbestreadsize fills this in) */
  32. int current_track = -1;        /* number of track currently playing */
  33. int stop_playing;        /* flag to indicate we're done */
  34. int last_mhi, last_mlo, last_shi, last_slo, last_fhi, last_flo;
  35.  
  36. ALport aport;
  37. CDPLAYER *cd;
  38. CDTRACKINFO info;
  39. CDPARSER *cdp;
  40. CDSTATUS status;
  41. CDFRAME *cdbuf;
  42.  
  43.  
  44. /* called for every frame - data is already byte swapped and de-emphasized */
  45. void
  46. cd_audio_callback (arg, type, data)
  47. int arg;
  48. CDDATATYPES type;
  49. void *data;
  50. {
  51. ALwritesamps (aport, data, SAMPS_PER_FRAME);
  52. }
  53.  
  54.  
  55. /*
  56.  * This callback is only enabled when the user specified a position to stop at
  57.  *    other than the default "end of a track".
  58.  *
  59.  * called for every frame only during the last track.
  60.  */
  61. void
  62. cd_ptime_callback (arg, type, data)
  63. int arg;
  64. CDDATATYPES type;
  65. struct cdtimecode *data;
  66. {
  67. if ((data->mhi == last_mhi) && (data->mlo == last_mlo) && (data->shi == last_shi)
  68.  && (data->slo == last_slo) && (data->fhi == last_fhi) && (data->flo == last_flo)) {
  69.     stop_playing++;
  70.     printf ("stopping at track %d time %d%d:%d%d frame %d%d\n", current_track,
  71.         data->mhi, data->mlo, data->shi, data->slo, data->fhi, data->flo);
  72.     }
  73. }
  74.  
  75.  
  76. /* called only when the program (track) number changes */
  77. void
  78. cd_pnum_callback (stop_at, type, data)
  79. int stop_at[4];
  80. CDDATATYPES type;
  81. struct cdprognum *data;
  82. {
  83. if (CDgettrackinfo(cd, data->value, &info) == 0) {
  84.     perror ("CDgettrackinfo (pnum callback) failed");
  85.     }
  86. else    {
  87.     current_track = data->value;
  88.  
  89.     /* stop if we've reached the end of the last whole track to play */
  90.     if ((stop_at[POS_MINUTE] == -1) && (current_track > stop_at[POS_TRACK])) {
  91.         stop_playing++;    /* stop playing now */
  92.         return;
  93.         }
  94.  
  95.     /* set this callback only if we need to check last_min:sec:frame */
  96.     if ((stop_at[POS_MINUTE] != -1) && (current_track == stop_at[POS_TRACK])) {
  97.         last_mhi = stop_at[POS_MINUTE] / 10;
  98.         last_mlo = stop_at[POS_MINUTE] % 10;
  99.         last_shi = stop_at[POS_SECOND] / 10;
  100.         last_slo = stop_at[POS_SECOND] % 10;
  101.         last_fhi = stop_at[POS_FRAME] / 10;
  102.         last_flo = stop_at[POS_FRAME] % 10;
  103.         CDsetcallback (cdp, cd_ptime, cd_ptime_callback, 0);
  104.         }
  105.     printf ("Track %2d:  Length %02d:%02d\n", current_track, info.total_min, info.total_sec);
  106.     }
  107. }
  108.  
  109.  
  110. /* called only when the index (sub-program division) number changes */
  111. void
  112. cd_index_callback (arg, type, data)
  113. int arg;
  114. CDDATATYPES type;
  115. struct cdprognum *data;
  116. {
  117. /* the index value is really only interesting if there is more than one */
  118. /* it might be nice to find out what timecode these happen at */
  119. /* I've tried using CDgetstatus(), and it resets the CD drive to the beginning of the disk */
  120.  
  121. printf ("Track %2d:  Index %2d\n", current_track, data->value);
  122.  
  123. /* FYI:  Index zero is normally a 2-3 second pause between tracks - CDseek and CDseektrack skip over it */
  124. /* Index one is normally the start of a song.  A few CDs are reported to have more indexes in them, most don't */
  125. }
  126.  
  127.  
  128. /*
  129.  * play selected audio data
  130.  */
  131.  
  132. void
  133. play_tracks (start_at, stop_at)
  134. int start_at[4];
  135. int stop_at[4];
  136. {
  137. int ctr, frames, first_secs, last_secs, total_secs, start_secs, start_min, start_sec;
  138.  
  139. /* check the stop_at values (if they were specified) */
  140. if (stop_at[POS_MINUTE] != -1) {
  141.     if (CDgettrackinfo(cd, stop_at[POS_TRACK], &info) == 0) {
  142.         perror ("CDgettrackinfo (play_track 2) failed");
  143.         return;
  144.         }
  145.     total_secs = (info.total_min * 60) + info.total_sec;
  146.     last_secs = (stop_at[POS_MINUTE] * 60) + stop_at[POS_SECOND];
  147.     if (last_secs > total_secs) {
  148.         printf ("track %d is only %02d:%02d:%02d long (cannot stop at %02d:%02d:%02d) !\n",
  149.             stop_at[POS_TRACK], info.total_min, info.total_sec, info.total_frame,
  150.             stop_at[POS_MINUTE], stop_at[POS_SECOND], stop_at[POS_FRAME]);
  151.         return;
  152.         }
  153.  
  154.     /* setup some values to make comparisons in cd_ptime_callback() faster */
  155.     last_mhi = stop_at[POS_MINUTE] / 10;
  156.     last_mlo = stop_at[POS_MINUTE] % 10;
  157.     last_shi = stop_at[POS_SECOND] / 10;
  158.     last_slo = stop_at[POS_SECOND] % 10;
  159.     last_fhi = stop_at[POS_FRAME] / 10;
  160.     last_flo = stop_at[POS_FRAME] % 10;
  161.     }
  162.  
  163. /*
  164.  * if the user wants to start at the beginning of a track,
  165.  * use the quick way to seek there
  166.  */
  167. if (start_at[POS_MINUTE] == -1) {
  168.     if (CDseektrack(cd, start_at[POS_TRACK]) == -1) {
  169.         perror ("CDseektrack");
  170.         return;
  171.         }
  172.     }
  173.  
  174. else    {    /* user wants to start at a specific point */
  175.     if (CDgettrackinfo(cd, start_at[POS_TRACK], &info) == 0) {
  176.         perror ("CDgettrackinfo (play_track 1) failed");
  177.         return;
  178.         }
  179.  
  180.     /* make sure the user didn't try to offset beyond the end of the song */
  181.     first_secs = (start_at[POS_MINUTE] * 60) + start_at[POS_SECOND];
  182.     total_secs = (info.total_min * 60) + info.total_sec;
  183.     if (first_secs > total_secs) {
  184.         printf ("track %d is only %02d:%02d:%02d long (cannot skip to %02d:%02d:%02d) !\n",
  185.             start_at[POS_TRACK], info.total_min, info.total_sec, info.total_frame,
  186.             start_at[POS_MINUTE], start_at[POS_SECOND], start_at[POS_FRAME]);
  187.         return;
  188.         }
  189.  
  190.     /* compute absolute position to seek to (position at start of song plus offset that the user supplied */
  191.     start_secs = (info.start_min * 60) + info.start_sec + first_secs;
  192.     start_min = start_secs / 60;
  193.     start_sec = start_secs % 60;
  194.     printf ("Track %d starts at absolute time %02d:%02d:%02d, seeking to absolute time %02d:%02d:%02d\n",
  195.         start_at[POS_TRACK], info.start_min, info.start_sec, info.start_frame,
  196.         start_min, start_sec, start_at[POS_FRAME]);
  197.  
  198.     if (CDseek(cd, start_min, start_sec, start_at[POS_FRAME]) == -1) {
  199.         perror ("CDseek");
  200.         exit (1);
  201.         }
  202.     }
  203.  
  204. current_track = start_at[POS_TRACK];
  205.  
  206. /* initialize parser structure */
  207. CDresetparser(cdp);
  208.  
  209. /* define callback routines for CDparseframe() */
  210. CDsetcallback (cdp, cd_audio,    cd_audio_callback, 0);        /* called for every frame */
  211. CDsetcallback (cdp, cd_pnum,    cd_pnum_callback, stop_at);    /* called only when the program (track) number changes */
  212. CDsetcallback (cdp, cd_index,    cd_index_callback, 0);        /* called only when the index (sub-program division) number changes */
  213.  
  214. stop_playing = 0;
  215.  
  216. /* play audio data until we have gone past the last track we want to play */
  217. while (stop_playing == 0) {
  218.     frames = CDreadda (cd, &cdbuf[0], read_size);
  219.     if (frames < 1) {
  220.         break;    /* EOF (or read error) */
  221.         }
  222.  
  223.     /*
  224.      *    SGI BUG!  The manpage for CDreadda() says it returns the number
  225.      *    of frames read.  In reality, it returns the number of bytes read
  226.      *    into the buffer.  Divide by CDDA_BLOCKSIZE to get the number of frames.
  227.      *
  228.      *    I'd prefer it to work like the manpage says, return the number of frames.
  229.      *    When this bug is fixed, delete the next line (and this comment).
  230.      */
  231.     frames = frames / CDDA_BLOCKSIZE;
  232.  
  233.     for (ctr = 0; (ctr < frames) && (ctr < read_size) && (stop_playing == 0); ctr++) {
  234.         CDparseframe (cdp, &cdbuf[ctr]);
  235.         }
  236.     }
  237. }
  238.  
  239.  
  240. /* signal handler - display where we are, so we can continue later */
  241. void
  242. cleanup ()
  243. {
  244. if (CDgetstatus(cd, &status) == 0) {
  245.     perror ("CDgetstatus (cleanup) failed");
  246.     }
  247. else    {
  248.     printf ("interrupted!  Continue by entering:  cdreader -f %d:%d:%d:%d\n",
  249.         status.track, status.min, status.sec, status.frame);
  250.     }
  251.  
  252. /* free parser memory */
  253. CDdeleteparser(cdp);
  254.  
  255. /* close CD player port */
  256. CDclose(cd);
  257.  
  258. /* close audio port */
  259. ALcloseport(aport);
  260.  
  261. exit (0);
  262. }
  263.  
  264.  
  265. /* interpret track[:minute[:second[:frame]]] argument for starting/ending times */
  266. int
  267. parse_timecode (timecode, arg)
  268. int timecode[4];
  269. char *arg;
  270. {
  271. int errors = 0, position = 0, ctr = 0;
  272. char *ptr, field[4];
  273.  
  274. for (ptr = arg; ; ptr++) {
  275.  
  276.     /* we'll take any of a colon, period or null to mark the end of the field
  277.        (null also marks end of the string - see below) */
  278.     if ((*ptr == ':') || (*ptr == '.') || (*ptr == '\0')) {    /* we've reached the end of the field */
  279.         field[ctr] = '\0';
  280.         switch (position) {
  281.  
  282.         case POS_TRACK:
  283.             timecode[POS_TRACK] = atoi(field);
  284.             /* we'll check this value later, after we find out which tracks exist */
  285.             break;
  286.  
  287.         case POS_MINUTE:
  288.             timecode[POS_MINUTE] = atoi(field);
  289.             if (timecode[POS_MINUTE] > 59) {
  290.                 errors++;
  291.                 fprintf (stderr, "invalid minute %d (valid range is 0 - 59)\n", timecode[POS_MINUTE]);
  292.                 }
  293.             break;
  294.  
  295.         case POS_SECOND:
  296.             timecode[POS_SECOND] = atoi(field);
  297.             if (timecode[POS_SECOND] > 59) {
  298.                 errors++;
  299.                 fprintf (stderr, "invalid second %d (valid range is 0 - 59)\n", timecode[POS_SECOND]);
  300.                 }
  301.             break;
  302.  
  303.         case POS_FRAME:
  304.             timecode[POS_FRAME] = atoi(field);
  305.             if (timecode[POS_FRAME] > 74) {
  306.                 errors++;
  307.                 fprintf (stderr, "invalid frame number %d (valid range is 0 - 74)\n", timecode[POS_FRAME]);
  308.                 }
  309.             break;
  310.  
  311.         default:
  312.             errors++;
  313.             fprintf (stderr, "error in format of timecode - too many colons\n");
  314.             break;
  315.             }
  316.  
  317.         if (*ptr == '\0') {    /* eof of string - that was the last field */
  318.             break;
  319.             }
  320.         else    {
  321.             /* reset for the next field */
  322.             position++;
  323.             ctr = 0;
  324.             }
  325.         }
  326.  
  327.     else if (ctr > 3) {
  328.         errors++;
  329.         fprintf (stderr, "error in format of timecode - too many digits in field %d\n", position);
  330.         break;
  331.         }
  332.  
  333.     else    {
  334.         field[ctr] = *ptr;
  335.         ctr++;
  336.         }
  337.     }
  338.  
  339. return (errors);
  340. }
  341.  
  342.  
  343. main (argc, argv)
  344. int argc;
  345. char *argv[];
  346. {
  347. long pvbuf[6];
  348. int c, ctr, errors = 0;
  349. int start_at[4], stop_at[4];
  350. int volume = -1;
  351.  
  352. fputs ("CDreader V1.0\n", stdout);
  353. start_at[POS_TRACK] = start_at[POS_MINUTE] = -1;
  354. start_at[POS_SECOND] = start_at[POS_FRAME] = 0;
  355. stop_at[POS_TRACK] = stop_at[POS_MINUTE] = -1;
  356. stop_at[POS_SECOND] = stop_at[POS_FRAME] = 0;
  357.  
  358. while ((c = getopt(argc, argv, "f:l:v:")) != -1) {
  359.     switch (c) {
  360.  
  361.     case 'f':    /* specify track, minute, second, frame to start at (everything except track # is optional) */
  362.         errors += parse_timecode (start_at, optarg);
  363.         if (errors == 0) {
  364.             printf ("will start at track %d", start_at[POS_TRACK]);
  365.             if (start_at[POS_MINUTE] != -1) {
  366.                 printf (", time %02d:%02d, frame %d\n", start_at[POS_MINUTE],
  367.                     start_at[POS_SECOND], start_at[POS_FRAME]);
  368.                 }
  369.             fputc ('\n', stdout);
  370.             }
  371.         break;
  372.  
  373.     case 'l':    /* specify last track to play */
  374.         errors += parse_timecode (stop_at, optarg);
  375.         if (errors == 0) {
  376.             if (stop_at[POS_MINUTE] != -1) {
  377.                 printf ("will stop at track %d, time %02d:%02d, frame %d\n",
  378.                     stop_at[POS_TRACK], stop_at[POS_MINUTE],
  379.                     stop_at[POS_SECOND], stop_at[POS_FRAME]);
  380.                 }
  381.             else    {
  382.                 printf ("will stop after track %d\n", stop_at[POS_TRACK]);
  383.                 }
  384.             fputc ('\n', stdout);
  385.             }
  386.         break;
  387.  
  388.     case 'v':    /* specify volume */
  389.         volume = atoi(optarg);
  390.         if ((volume < 0) || (volume > 255)) {
  391.             errors++;
  392.             fprintf (stderr, "invalid volume %d - valid range is 0-255\n", volume);
  393.             }
  394.         break;
  395.  
  396.     default:    /* anything else is an error */
  397.         errors++;
  398.         }
  399.     }
  400.  
  401. if (errors > 0) {
  402.     fprintf (stderr, "usage:  %s [-f track[:min[:sec[.frame]]]] [-l track[:min[:sec[.frame]]]] [-v volume]\n", argv[0]);
  403.     exit (1);
  404.     }
  405.  
  406. /* should really trap everything, right? */
  407. signal (SIGHUP, cleanup);
  408. signal (SIGINT, cleanup);
  409. signal (SIGQUIT, cleanup);
  410. signal (SIGTERM, cleanup);
  411.  
  412. cd = CDopen(0, "r");
  413. if (cd == NULL) {
  414.     perror ("CDopen failed");
  415.     exit (1);
  416.     }
  417.  
  418. /* display disk info */
  419. if (CDgetstatus(cd, &status) == 0) {
  420.     perror ("CDgetstatus failed");
  421.     exit (1);
  422.     }
  423.  
  424. if (status.state != CD_READY) {
  425.     fprintf (stderr, "The CD player is not ready\n");
  426.     CDclose(cd);
  427.     exit (1);
  428.     }
  429.  
  430. printf ("CD player is Ready, %d total tracks, total time on disk = %02d:%02d\n",
  431.     status.last, status.total_min, status.total_sec);
  432.  
  433. /* by default start at first track */
  434. if (start_at[POS_TRACK] == -1) {
  435.     start_at[POS_TRACK] = status.first;
  436.     }
  437. else if (start_at[POS_TRACK] < status.first) {
  438.     printf ("cannot start at track %d - first valid track is %d (we'll use that instead)\n", start_at[POS_TRACK], status.first);
  439.     start_at[POS_TRACK] = status.first;
  440.     }
  441. else if (start_at[POS_TRACK] > status.last) {
  442.     printf ("cannot start at track %d - last valid track is %d\n", start_at[POS_TRACK], status.last);
  443.     CDclose(cd);
  444.     exit (1);
  445.     }
  446.  
  447. /* by default play through last track */
  448. if (stop_at[POS_TRACK] == -1) {
  449.     stop_at[POS_TRACK] = status.last;
  450.     }
  451. else if (stop_at[POS_TRACK] < start_at[POS_TRACK]) {
  452.     printf ("you want to stop before you start?\n", stop_at[POS_TRACK], start_at[POS_TRACK]);
  453.     CDclose(cd);
  454.     exit (1);
  455.     }
  456. else if (stop_at[POS_TRACK] < status.first) {
  457.     printf ("cannot stop after track %d - first valid track is %d\n", stop_at[POS_TRACK], status.first);
  458.     CDclose(cd);
  459.     exit (1);
  460.     }
  461. else if (stop_at[POS_TRACK] > status.last) {
  462.     printf ("cannot stop after track %d - last valid track is %d (we'll use that instead)\n", start_at[POS_TRACK], status.last);
  463.     stop_at[POS_TRACK] = status.last;
  464.     }
  465.  
  466. /* initialize audio port */
  467. aport = ALopenport ("cdreader", "w", (ALconfig) 0);
  468. if (aport == (ALport) 0) {
  469.     fprintf (stderr, "Could not open audio port\n");
  470.     CDclose(cd);
  471.     exit (1);
  472.     }
  473.  
  474. /* set audio port output sampling rate */
  475. pvbuf[0] = AL_OUTPUT_RATE;
  476. pvbuf[1] = AL_RATE_44100;
  477. ALsetparams (AL_DEFAULT_DEVICE, pvbuf, 2);
  478.  
  479. /* get the current volume */
  480. pvbuf[0] = AL_LEFT_SPEAKER_GAIN;
  481. pvbuf[2] = AL_RIGHT_SPEAKER_GAIN;
  482. ALgetparams (AL_DEFAULT_DEVICE, pvbuf, 4);
  483.  
  484. /* set volume, if the user specified a value */
  485. if (volume > -1) {
  486.     pvbuf[1] = pvbuf[3] = volume;
  487.     ALsetparams (AL_DEFAULT_DEVICE, pvbuf, 4);
  488.     }
  489.  
  490. /* check for very low volume */
  491. if ((pvbuf[1] < LOW_VOLUME) || (pvbuf[3] < LOW_VOLUME)) {
  492.     fputs ("warning!  Your volume is set awfully low\n", stdout);
  493.     }
  494.  
  495. /* create parser structure */
  496. cdp = CDcreateparser();
  497. if (cdp == NULL) {
  498.     perror ("CDcreateparser");
  499.     CDclose(cd);
  500.     ALcloseport(aport);
  501.     exit (1);
  502.     }
  503.  
  504. /* find the best size to read */
  505. read_size = CDbestreadsize(cd);
  506.  
  507. /* allocate a buffer to read into */
  508. cdbuf = (CDFRAME *) malloc (read_size * CDDA_BLOCKSIZE);
  509.  
  510. /* play all tracks selected */
  511. play_tracks (start_at, stop_at);
  512.  
  513. /* free parser memory */
  514. CDdeleteparser(cdp);
  515.  
  516. /* close CD player port */
  517. CDclose(cd);
  518.  
  519. /* close audio port */
  520. ALcloseport(aport);
  521.  
  522. exit (0);
  523. }
  524.